/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef ARMV8_A_PAGETABLE_H
#define ARMV8_A_PAGETABLE_H

#include <stdlib/stdint.h>
#include <stdlib/string.h>
#include <tools/macros.h>

#define INNER_SHAREABLE (0x3)

#define MMU_MAIR_ATTR1 (0x04 << (8 * 1))

#define MMU_MAIR_ATTR2 (0xff << (8 * 2))

#define MMU_MAIR_ATTR3 (0x44 << (8 * 3))

#define NORMAL_MEMORY (0x2)
#define DEVICE_MEMORY (0x1)

#define AARCH64_PTE_AP_HIGH_RW_EL0_NA (0)
#define AARCH64_PTE_AP_HIGH_RW_EL0_RW (1)
#define AARCH64_PTE_AP_HIGH_RO_EL0_NA (2)
#define AARCH64_PTE_AP_HIGH_RO_EL0_RO (3)

#define GET_ADDR(x) (x & (0xFFFFFFFFFUL << PAGE_SHIFT))

#define AARCH64_PTE_UX  (0)
#define AARCH64_PTE_UXN (1)
#define AARCH64_PTE_PXN (1)

#define AARCH64_PTE_AF_ACCESSED (1)

#define AARCH64_PTE_GLOBAL  (0)
#define AARCH64_PTE_NGLOBAL (1)

#define AARCH64_PTE_INVALID_MASK (1 << 0)
#define AARCH64_PTE_TABLE_MASK   (1 << 1)
#define IS_PTE_INVALID(pte)      (!((pte) & AARCH64_PTE_INVALID_MASK))
#define IS_PTE_TABLE(pte)        ((pte) & AARCH64_PTE_TABLE_MASK)

#define PGD_SHIFT  (39)
#define PUD_SHIFT  (30)
#define PMD_SHIFT  (21)
#define PAGE_SHIFT (12)

#define PGD_SIZE  (1UL << PGD_SHIFT)
#define PMD_SIZE  (1UL << PMD_SHIFT)
#define PUD_SIZE  (1UL << PUD_SHIFT)
#define PAGE_SIZE (1UL << PAGE_SHIFT)

#define PGD_MASK   (PGD_SIZE - 1)
#define PMD_MASK   (PMD_SIZE - 1)
#define PUD_MASK   (PUD_SIZE - 1)
#define PAGE_MASK  (PAGE_SIZE - 1)
#define TABLE_MASK (((1UL << 48) - 1) & ~(PAGE_MASK))

#define PAGE_ORDER (9)

#define PTP_INDEX_MASK ((1 << (PAGE_ORDER)) - 1)
#define L0_INDEX_SHIFT ((3 * PAGE_ORDER) + PAGE_SHIFT)
#define L1_INDEX_SHIFT ((2 * PAGE_ORDER) + PAGE_SHIFT)
#define L2_INDEX_SHIFT ((1 * PAGE_ORDER) + PAGE_SHIFT)
#define L3_INDEX_SHIFT ((0 * PAGE_ORDER) + PAGE_SHIFT)

#define GET_L0_INDEX(addr) ((addr >> L0_INDEX_SHIFT) & PTP_INDEX_MASK)
#define GET_L1_INDEX(addr) ((addr >> L1_INDEX_SHIFT) & PTP_INDEX_MASK)
#define GET_L2_INDEX(addr) ((addr >> L2_INDEX_SHIFT) & PTP_INDEX_MASK)
#define GET_L3_INDEX(addr) ((addr >> L3_INDEX_SHIFT) & PTP_INDEX_MASK)

#define PTP_ENTRIES (1UL << PAGE_ORDER)

#define L0_PER_ENTRY_PAGES ((PTP_ENTRIES) * (L1_PER_ENTRY_PAGES))
#define L1_PER_ENTRY_PAGES ((PTP_ENTRIES) * (L2_PER_ENTRY_PAGES))
#define L2_PER_ENTRY_PAGES ((PTP_ENTRIES) * (L3_PER_ENTRY_PAGES))
#define L3_PER_ENTRY_PAGES (1)

#define L1_BLOCK_MASK ((L1_PER_ENTRY_PAGES << PAGE_SHIFT) - 1)
#define L2_BLOCK_MASK ((L2_PER_ENTRY_PAGES << PAGE_SHIFT) - 1)
#define L3_PAGE_MASK  ((L3_PER_ENTRY_PAGES << PAGE_SHIFT) - 1)

#define GET_VA_OFFSET_L1(va) (va & L1_BLOCK_MASK)
#define GET_VA_OFFSET_L2(va) (va & L2_BLOCK_MASK)
#define GET_VA_OFFSET_L3(va) (va & L3_PAGE_MASK)

#define ALIGN_UP(addr, size)   (((addr) + ((size)-1)) & (~((typeof(addr))(size)-1)))
#define ALIGN_DOWN(addr, size) ((addr) & (~((typeof(addr))(size)-1)))

#define PAGE_ALIGN_UP(addr)   ALIGN_UP(addr, PAGE_SIZE)
#define PAGE_ALIGN_DOWN(addr) ALIGN_DOWN(addr, PAGE_SIZE)

typedef union {
    struct {
        uint64_t is_valid : 1, is_table : 1, ignored1 : 10, next_table_addr : 36, reserved : 4, ignored2 : 7,
            PXNTable : 1, XNTable : 1, APTable : 2, NSTable : 1;
    } table;
    struct {
        uint64_t is_valid : 1, is_table : 1, attr_index : 3, NS : 1, AP : 2, SH : 2, AF : 1, nG : 1, reserved1 : 4,
            nT : 1, reserved2 : 13, pfn : 18, reserved3 : 2, GP : 1, reserved4 : 1, DBM : 1, Contiguous : 1, PXN : 1,
            UXN : 1, soft_reserved : 4, PBHA : 4;
    } l1_block;
    struct {
        uint64_t is_valid : 1, is_table : 1, attr_index : 3, NS : 1, AP : 2, SH : 2, AF : 1, nG : 1, reserved1 : 4,
            nT : 1, reserved2 : 4, pfn : 27, reserved3 : 2, GP : 1, reserved4 : 1, DBM : 1, Contiguous : 1, PXN : 1,
            UXN : 1, soft_reserved : 4, PBHA : 4;
    } l2_block;
    struct {
        uint64_t is_valid : 1, is_page : 1, attr_index : 3, NS : 1, AP : 2, SH : 2, AF : 1, nG : 1, pfn : 36,
            reserved : 3, DBM : 1, Contiguous : 1, PXN : 1, UXN : 1, soft_reserved : 4, PBHA : 4, ignored : 1;
    } l3_page;
    uint64_t pte;
} pte_t;

#define PTE_DESCRIPTOR_INVALID (0)

typedef struct page_table {
    pte_t entry[PTP_ENTRIES];
} page_table_t ALIGNED(PAGE_SIZE);

int  page_table_recycle(page_table_t *pd, int level);
void page_table_copy(page_table_t *des, page_table_t *src, int level);

uint64_t page_table_missing_pt(page_table_t *pd, uint64_t vbase, uint64_t size, uint64_t level);
int      page_table_map(page_table_t *pd, uint64_t pbase, uint64_t vbase, uint64_t size, uint64_t attr);
int      page_table_unmap(page_table_t *pd, uint64_t va, size_t len);
void     page_table_init(page_table_t *pd);

void page_table_switch_to(page_table_t *pd, int iskernel);
void page_table_switch_to_asid(page_table_t *pd, unsigned short asid, int iskernel);

void     page_table_active(page_table_t *pd, int iskernel);
int      verify_va_to_pa(page_table_t *pgtbl, uint64_t va, uint64_t t_pa);
uint64_t trans_va_into_pa(page_table_t *pgtbl, uint64_t va);

int      split_block_into_table(page_table_t *pgtbl, pte_t *cur_pte, uint64_t va, size_t len, int level);
uint64_t get_attr_from_pte(pte_t *pte);
int get_next_ptp(page_table_t *cur_ptp, uint8_t level, uint64_t va, page_table_t **next_ptp, pte_t **pte, bool_t alloc);
#endif
